package com.xique.park.service.biz.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.xique.common.core.constant.CommandResultCode;
import com.xique.common.core.constant.Constants;
import com.xique.common.core.constant.HttpStatus;
import com.xique.common.core.domain.R;
import com.xique.common.core.enums.LaneTypeEnum;
import com.xique.common.core.exception.CustomException;
import com.xique.common.core.utils.AliossUtil;
import com.xique.common.core.utils.DateUtils;
import com.xique.common.core.utils.SecurityUtils;
import com.xique.common.core.utils.StringUtils;
import com.xique.common.core.utils.poi.ExcelUtil;
import com.xique.common.core.web.domain.AjaxResult;
import com.xique.park.bean.domain.*;
import com.xique.park.bean.dto.ParkOutDto;
import com.xique.park.bean.request.comm.EditParkOnRequest;
import com.xique.park.bean.request.comm.MarkOutParkOnRequest;
import com.xique.park.bean.request.comm.ParkOnEditPlateSaveRequest;
import com.xique.park.bean.request.comm.ParkOnListRequest;
import com.xique.park.bean.request.web.parkOn.BatchMarkOutParkOnRequest;
import com.xique.park.bean.transform.comm.ParkOnTransform;
import com.xique.park.bean.vo.comm.ParkOnEditPlateVo;
import com.xique.park.bean.vo.comm.ParkOnVo;
import com.xique.park.command.handler.MarkCarInoutCommandHandler;
import com.xique.park.constant.DictConstant;
import com.xique.park.constant.ParkOnConstant;
import com.xique.park.service.biz.service.IParkOnBizService;
import com.xique.park.service.biz.service.IParkSpaceBizService;
import com.xique.park.service.command.service.IParkGarageCommandService;
import com.xique.park.service.service.*;
import com.xique.park.utils.RedisHandler;
import com.xique.system.api.RemoteSystemProjectService;
import com.xique.system.api.RemoteSystemUserService;
import com.xique.system.api.dto.OperateDTO;
import com.xique.system.api.dto.SystemProjectDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 在场记录
 *
 * @author xique
 */
@Slf4j
@Service
public class ParkOnBizServiceImpl implements IParkOnBizService {

    @Autowired
    private IParkOnService parkOnService;

    @Autowired
    private IParkOutService parkOutService;

    @Autowired
    private AliossUtil aliossUtil;

    @Autowired
    private IParkGarageService parkGarageService;

    @Autowired
    private IParkLaneService parkLaneService;

    @Autowired
    private IParkCarMngtypeService parkCarMngtypeService;

    @Autowired
    private IParkSpaceBizService parkSpaceBizService;

    @Autowired
    private RemoteSystemUserService remoteSystemUserService;

    @Autowired
    private RemoteSystemProjectService remoteSystemProjectService;

    @Autowired
    private IParkPlatemodifyRecordServce parkPlatemodifyRecordServce;

    @Autowired
    private IParkingOrderService parkingOrderService;

    @Autowired
    private IParkingOrderOnService parkingOrderOnService;

    @Autowired
    private IParkingOrderEsService parkingOrderEsService;

    @Autowired
    private IParkInService parkInService;

    @Autowired
    private IParkConfigService parkConfigService;

    @Autowired
    private IParkGarageCommandService parkGarageCommandService;

    @Autowired
    private IParkDeviceNewService parkDeviceNewService;

    @Autowired
    private IParkInfoService parkInfoService;

    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private MarkCarInoutCommandHandler markCarInoutCommandHandler;

    @Autowired
    private RedisHandler redisHandler;

    @Override
    public AjaxResult getParkOnLst(ParkOnListRequest request) {
        String isCountSpace = request.getIsCountSpace();
        if (StrUtil.isNotBlank(isCountSpace) && DictConstant.YES.equals(isCountSpace)) {
            // 分车位统计
            return isCountSpaceList(request);
        } else {
            // 不分车位统计
            request.setParkId(SecurityUtils.getParkId());
            IPage<ParkOn> page = parkOnService.selectParkOnPage(request);
            List<ParkOn> parkOnList = page.getRecords();

            // 赋值操作人
            List<ParkOnVo> parkOnVoList = handleUpdateUser(parkOnList);

            Map<String, Object> map = new HashMap<>(2);
            map.put(Constants.LIST, parkOnVoList);
            map.put(Constants.TOTAL, page.getTotal());
            return AjaxResult.success(map);
        }
    }

    /**
     * 分车位统计
     */
    private AjaxResult isCountSpaceList(ParkOnListRequest request) {
        long parkId = SecurityUtils.getParkId();
        Map<String, Object> map = new HashMap<>(2);

        // 判断车场是否开启剩余车位统计
        boolean enGarageSpaces = enGarageSpaces(parkId);
        if (!enGarageSpaces) {
            map.put(Constants.LIST, ListUtil.empty());
            map.put(Constants.TOTAL, 0);
            return AjaxResult.success(map);
        }

        // 判断搜索条件是否包含车库，如果包含的话，就查询单个车库，未包含的话就查询车场下的全部车库
        Long garageId = request.getGarageId();
        List<ParkGarage> parkGarageList = new ArrayList<>();
        if (null == garageId) {
            // 查询项目下的车场信息
            parkGarageList = parkGarageService.selectParkGarageByParkId(parkId);
            if (CollUtil.isEmpty(parkGarageList)) {
                map.put(Constants.LIST, ListUtil.empty());
                map.put(Constants.TOTAL, 0);
                return AjaxResult.success(map);
            }
        } else {
            ParkGarage parkGarage = parkGarageService.selectParkGarageById(garageId);
            if (null == parkGarage) {
                map.put(Constants.LIST, ListUtil.empty());
                map.put(Constants.TOTAL, 0);
                return AjaxResult.success(map);
            }
            parkGarageList.add(parkGarage);
        }

        // 根据参数车位统计的类型取每个车场的在场车辆列表
        request.setParkId(SecurityUtils.getParkId());

        List<ParkOn> parkOnAllList = new ArrayList<>();
        String allType = "0";
        parkGarageList.forEach(parkGarage -> {
            // 车场参数车位统计的管理类型
            String countSpaceMngtype = parkGarage.getCountSpaceMngtype();
            List<ParkOn> parkOnListByGarage;
            // 为空或者为0的话代表查询全部的管理类型
            if (StrUtil.isEmpty(countSpaceMngtype) || allType.equals(countSpaceMngtype)) {
                // 查询全部
                request.setCarMngTypeIds(null);
            } else {
                // 查询特定类型
                request.setCarMngTypeIds(countSpaceMngtype.split(","));
            }
            request.setInStatusArray(ParkOnConstant.passStatus.split(","));
            request.setGarageId(parkGarage.getId());

            parkOnListByGarage = parkOnService.selectParkOnAllList(request);
            if (CollUtil.isNotEmpty(parkOnListByGarage)) {
                // 合并列表
                parkOnAllList.addAll(parkOnListByGarage);
            }
        });

        // 分页取值
        if (CollUtil.isNotEmpty(parkOnAllList)) {
            int pageNum = request.getPageNum();
            int pageSize = request.getPageSize();
            int totalSize = parkOnAllList.size();
            int startSize = pageNum * pageSize - pageSize + 1;
            int endSize = pageNum * pageSize;
            if (endSize > totalSize) {
                endSize = totalSize;
                startSize = (endSize / pageSize) * pageSize + 1;
            }
            log.info("【在场车辆列表】根据分车位统计，起始页：{}，结束页：{}", startSize - 1, endSize - 1);
            List<ParkOn> resultList = parkOnAllList.subList(startSize - 1, endSize);

            // 赋值操作人
            List<ParkOnVo> parkOnVoList = handleUpdateUser(resultList);

            map.put(Constants.LIST, parkOnVoList);
            map.put(Constants.TOTAL, totalSize);
        } else {
            map.put(Constants.LIST, ListUtil.empty());
            map.put(Constants.TOTAL, 0);
        }
        return AjaxResult.success(map);
    }

    /**
     * 赋值操作人
     */
    private List<ParkOnVo> handleUpdateUser(List<ParkOn> parkOnList) {
        List<ParkOnVo> parkOnVoList = new ArrayList<>();

        if (CollUtil.isNotEmpty(parkOnList)) {
            StringBuilder operateIds = new StringBuilder();
            parkOnList.forEach(parkOn -> {
                // 转成 ParkOnVo
                ParkOnVo parkOnVo = ParkOnTransform.transformParkOnVo(parkOn);
                if (null != parkOn.getUpdateUser()) {
                    parkOnVo.setUpdateUser(String.valueOf(parkOn.getUpdateUser()));
                }
                parkOnVoList.add(parkOnVo);
                // 拼接操作人ID
                Long updateUserId = parkOn.getUpdateUser();
                if (updateUserId != null) {
                    operateIds.append(parkOn.getUpdateUser()).append(",");
                }
            });

            // 赋值操作人信息
            R<List<OperateDTO>> operateResult = remoteSystemUserService.getOperateList(operateIds.toString());
            if (operateResult.getData() != null) {
                List<OperateDTO> operateList = operateResult.getData();
                if (CollUtil.isNotEmpty(operateList)) {
                    Map<Long, String> operateMap = operateList.stream().collect(Collectors.toMap(OperateDTO::getOperateId, OperateDTO::getOperateName));
                    parkOnVoList.forEach(parkOnVo -> {
                        if (StrUtil.isNotBlank(parkOnVo.getUpdateUser())) {
                            String operateName = operateMap.get(Long.parseLong(parkOnVo.getUpdateUser()));
                            parkOnVo.setUpdateUser(operateName);
                        }
                    });
                }
            }
        }
        return parkOnVoList;
    }

    /**
     * 判断车场是否开启剩余车位统计
     */
    private boolean enGarageSpaces(Long parkId) {
        ParkConfig parkConfig = parkConfigService.selectParkConfigByParkId(parkId);
        if (null == parkConfig) {
            return false;
        }
        String config = parkConfig.getConfig();
        if (StrUtil.isBlank(config)) {
            return false;
        }
        JSONObject configJsonObj = (JSONObject) JSON.parse(config);
        String enGarageSpaces = configJsonObj.getString("enRemain");
        return !DictConstant.NO.equals(enGarageSpaces);
    }

    @Override
    public AjaxResult getParkOnEditPlateInfo(EditParkOnRequest request) {
        ParkOn parkOn = parkOnService.selectParkOnById(request.getRecordId());
        if (null != parkOn) {
            String inPicObjectName = parkOn.getInPic();
            parkOn.setInPic(StringUtils.isNotEmpty(inPicObjectName) ? aliossUtil.getJzyGetObjectUrl(inPicObjectName).toString() : "");
            ParkOnEditPlateVo vo = ParkOnTransform.transformParkOnEditPlateVo(parkOn);
            return AjaxResult.success(vo);
        } else {
            return AjaxResult.error("未查询到在场记录");
        }
    }

    /**
     * 暂时只修改在场记录
     * 除了修改在场记录，还应修改其他，修改后车牌能正常出场；
     * 增加一个车牌修改记录表
     */
    @Override
    public AjaxResult editPlateInfo(ParkOnEditPlateSaveRequest request) {
        // 判断该车场下的该车辆是否已经在场
        String newPlate = request.getNewPlate();
        long parkId = SecurityUtils.getParkId();

        // 在场记录
        ParkOn parkOn = parkOnService.selectParkOnById(request.getRecordId());

        if (!newPlate.equals(parkOn.getPlate())) {
            //如果修改的车牌和原车牌不一致，判断新车牌是否已存在入场记录
            List<ParkOn> parkOnList = parkOnService.selectParkOnByPlate(parkId, newPlate);
            if (CollUtil.isNotEmpty(parkOnList)) {
                return AjaxResult.error("该车牌已在场");
            }
        }

        // 车牌修改记录
        ParkPlatemodifyRecord record = new ParkPlatemodifyRecord();
        record.setParkId(SecurityUtils.getParkId());
        record.setOriginalPlate(parkOn.getPlate());
        record.setLaneId(parkOn.getInLane());
        record.setModifiedPlate(request.getNewPlate());
        record.setOriginalMngtypeId(parkOn.getCarMngtypeId());
        record.setModifiedMngtypeId(request.getNewMngTypeId());
        record.setTime(parkOn.getInTime());
        record.setInId(parkOn.getInId());
        record.setUpdateUser(SecurityUtils.getUserId());
        record.setType("in");

        // 修改在场信息
        parkOn.setPlate(request.getNewPlate());
        parkOn.setCarMngtypeId(request.getNewMngTypeId());
        parkOn.setIsUpdate("Y");
        parkOn.setUpdateUser(SecurityUtils.getUserId());
        parkOn.setUpdateTime(DateUtils.getNowDate());

        // 手动开启事务！
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            int count = parkOnService.updateParkOn(parkOn);
            if (count < 0) {
                // 回滚
                dataSourceTransactionManager.rollback(transactionStatus);
                return AjaxResult.error("在场记录修改失败");
            }
            boolean bool = parkPlatemodifyRecordServce.save(record);
            if (!bool) {
                // 回滚
                dataSourceTransactionManager.rollback(transactionStatus);
                return AjaxResult.error("车牌修改记录保存失败");
            }
            // 修改入场信息 BY WLD 2022/4/15
            long parkInId = parkOn.getInId();
            ParkIn parkIn = new ParkIn();
            parkIn.setId(parkInId);
            parkIn.setInCarPlate(newPlate);
            parkIn.setMngTypeId(request.getNewMngTypeId());
            parkIn.setUpdateTime(new Date());
            parkIn.setUpdateUser(SecurityUtils.getUserId());
            parkInService.updateById(parkIn);

            // 修改订单信息 BY WLD 2022/4/15
            String parkingSerial = parkOn.getParkingSerial();
            ParkingOrder parkingOrder = new ParkingOrder();
            parkingOrder.setParkingSerial(parkingSerial);
            parkingOrder.setPlate(newPlate);
            parkingOrder.setMngtypeId(request.getNewMngTypeId());
            parkingOrderService.updateByParkingSerial(parkingOrder);

            // 修改在场订单信息 BY WLD 2022/4/15
            ParkingOrderOn parkingOrderOn = new ParkingOrderOn();
            parkingOrderOn.setParkingSerial(parkingSerial);
            parkingOrderOn.setPlate(newPlate);
            parkingOrderOn.setMngtypeId(request.getNewMngTypeId());
            parkingOrderOnService.updateByParkingSerial(parkingOrderOn);

            // 提交
            dataSourceTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 回滚
            dataSourceTransactionManager.rollback(transactionStatus);
            return AjaxResult.error("车牌修改处理发生异常，请联系管理员！");
        }

        // 剩余车位下发处理
        if (request.getNewMngTypeId() != null && !request.getMngTypeId().equals(request.getNewMngTypeId())) {
            // 车辆管理类型做了修改，判断新类型是否为该车库统计剩余车位的类型，是就重新下发(0代表全部管理类型都参与车位统计)
            Long inLaneId = parkOn.getInLane();
            ParkLane parkLane = parkLaneService.selectParkLaneById(inLaneId);
            ParkGarage parkGarage = parkGarageService.selectParkGarageById(parkLane.getGarageId());
            String countSpaceMngtype = parkGarage.getCountSpaceMngtype();
            if (StringUtils.isNotEmpty(countSpaceMngtype)) {
                List<String> typeIds = Arrays.asList(StringUtils.split(countSpaceMngtype, ","));
                typeIds.forEach(e -> {
                    if (e.equals(request.getNewMngTypeId().toString()) || "0".equals(e) || e.equals(request.getMngTypeId().toString())) {
                        parkGarageCommandService.updateSpaces(parkGarage.getParkId(), parkGarage.getId());
                    }
                });
            }
        }

        return AjaxResult.success("操作成功");
    }

    /**
     * 标记出场
     * 1、删除在场记录；
     * 2、生成出场记录;
     * 3、给设备更新剩余车位；
     */
    @Override
    public AjaxResult markOut(MarkOutParkOnRequest request) {
        ParkOn parkOn = parkOnService.selectParkOnById(request.getRecordId());
        if (null != parkOn) {
            // 手动开启事务！
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

            ParkCarMngtype parkCarMngtype = parkCarMngtypeService.selectParkCarMngtypeById(parkOn.getCarMngtypeId());

            ParkLane parkLane = parkLaneService.selectParkLaneById(parkOn.getInLane());

            try {
                // 更新出场记录
                ParkOut parkOut = updateParkOut(parkOn, parkCarMngtype, request.getStatus(), request.getOutRemark());

                // 删除在场记录
                parkOnService.deleteParkOnById(request.getRecordId());

                // 修改订单记录
                updateParkingOrder(parkOut, parkOn, parkCarMngtype, parkLane);

                // 删除在场的车辆订单
                parkingOrderOnService.deleteParkingOrderOnByParkingSerial(parkOn.getParkingSerial());

                //查询所有外场通道的通道订单，如果车牌为当前车牌，修改状态
                updateLaneOrder(Collections.singletonList(parkOn.getPlate()));

                // 提交
                dataSourceTransactionManager.commit(transactionStatus);
            } catch (Exception e) {
                e.printStackTrace();
                // 回滚
                dataSourceTransactionManager.rollback(transactionStatus);
                return AjaxResult.error("标记离场处理发生异常，请联系管理员！");
            }

            // 标记离场，重新下发余位
            if (parkLane != null) {
                AjaxResult result = parkGarageCommandService.updateSpaces(parkLane.getParkId(), parkLane.getGarageId());
                if (result.get(AjaxResult.CODE_TAG).equals(HttpStatus.ERROR)) {
                    return AjaxResult.error("更新剩余车位命令下发失败，失败原因：" + result.get(AjaxResult.MSG_TAG));
                }
            }
            return AjaxResult.success("操作成功");
        } else {
            return AjaxResult.error("未查询到在场记录");
        }
    }

    private void updateLaneOrder(List<String> plates) {
        List<ParkLane> parkLaneList = parkLaneService.selectParkLaneByParkId(SecurityUtils.getParkId(), LaneTypeEnum.OUT.getType());
        for (ParkLane lane : parkLaneList) {
            //遍历所有外场通道
            JSONObject laneParkingOrder = redisHandler.getLaneParkingOrder(lane.getId());
            if (null == laneParkingOrder) {
                continue;
            }
            ParkingOrderOn parkingOrderOn = laneParkingOrder.getObject("ParkingOrder", ParkingOrderOn.class);
            if (plates.contains(parkingOrderOn.getPlate()) && "on".equals(parkingOrderOn.getParkStatus())) {
                //匹配需要离场的车牌和通道内的车牌是否一致，是则标记为离场
                parkingOrderOn.setParkStatus("out");
                redisHandler.saveLaneParkingOrder(laneParkingOrder.getLong("laneId"), parkingOrderOn, System.currentTimeMillis());
            }
        }
    }

    private ParkOut updateParkOut(ParkOn parkOn, ParkCarMngtype parkCarMngtype, String status, String outRemark) {
        ParkOut parkOut = new ParkOut();
        parkOut.setInId(parkOn.getInId());
        parkOut.setParkId(SecurityUtils.getParkId());
        parkOut.setCarType(parkOn.getCarType());
        parkOut.setActualFee(0L);
        parkOut.setOutCarPlate(parkOn.getPlate());
        parkOut.setOutLaneId(null);
        // 人工确认出场
        parkOut.setStatus(status);
        parkOut.setOutRemark(outRemark);
        parkOut.setOutTime(new Date());
        parkOut.setUpdateUser(SecurityUtils.getUserId());
        parkOut.setIsUpdate("Y");
        parkOut.setParkingSerial(parkOn.getParkingSerial());
        parkOut.setMngType(parkOn.getMngType());

        if (parkCarMngtype != null) {
            parkOut.setMngTypeId(parkOn.getCarMngtypeId());
            parkOut.setMngType(parkCarMngtype.getMngType());
        }

        // 判断是否已经存在出场记录
        String parkingSerial = parkOn.getParkingSerial();
        List<ParkOutDto> parkOutDtoList = parkOutService.selectParkOutByParkingSerial(SecurityUtils.getParkId(), parkingSerial);
        if (CollUtil.isNotEmpty(parkOutDtoList)) {
            long parkOutId = parkOutDtoList.get(0).getId();
            // 更新出场记录
            parkOut.setId(parkOutId);
            parkOutService.updateParkOut(parkOut);
        } else {
            // 新增出场记录
            parkOutService.insertParkOut(parkOut);
        }

        return parkOut;
    }

    private void updateParkingOrder(ParkOut parkOut, ParkOn parkOn, ParkCarMngtype parkCarMngtype, ParkLane parkLane) {
        Long parkId = parkOn.getParkId();
        String parkingSerial = parkOn.getParkingSerial();

        ParkingOrder parkingOrderSearch = new ParkingOrder();
        parkingOrderSearch.setParkId(parkId);
        parkingOrderSearch.setParkingSerial(parkingSerial);
        List<ParkingOrder> parkingOrderList = parkingOrderService.selectParkingOrderList(parkingOrderSearch);

        if (CollUtil.isNotEmpty(parkingOrderList)) {
            ParkingOrder parkingOrder = parkingOrderList.get(0);
            // 赋值车场信息
            R<SystemProjectDTO> systemProjectR = remoteSystemProjectService.getProjectInfo(parkId);
            if (null != systemProjectR && null != systemProjectR.getData()) {
                parkingOrder.setParkName(systemProjectR.getData().getName());
            }
            // 获取入场信息
            ParkIn parkInSearch = new ParkIn();
            parkInSearch.setParkId(parkId);
            parkInSearch.setParkingSerial(parkingSerial);
            List<ParkIn> parkInList = parkInService.selectParkInList(parkInSearch);
            if (CollUtil.isNotEmpty(parkInList)) {
                // 订单中赋值进场信息
                ParkIn parkIn = parkInList.get(0);
                parkingOrder.setInCarPlate(parkIn.getInCarPlate());
                parkingOrder.setInTime(parkIn.getInTime());
                parkingOrder.setInPic(parkIn.getInPic());
                parkingOrder.setInType(parkIn.getInType());
                parkingOrder.setInStatus(parkIn.getStatus());
                parkingOrder.setInRemark(parkIn.getInRemark());
                parkingOrder.setInUpdateUser(parkIn.getUpdateUser());
                parkingOrder.setMngType(parkIn.getMngType());
                parkingOrder.setCarMngtypeName(parkCarMngtype != null ? parkCarMngtype.getName() : "");
                parkingOrder.setInLaneName(parkLane != null ? parkLane.getName() : "");
            } else {
                log.warn("[在场记录服务]-手动标记车辆出场，更新车辆订单信息，车辆进场记录不存在：{}", parkingOrder);
            }

            // 订单中赋值出场信息
            parkingOrder.setOutCarPlate(parkOut.getOutCarPlate());
            parkingOrder.setOutTime(parkOut.getOutTime());
            parkingOrder.setOutStatus(parkOut.getStatus());
            parkingOrder.setOutRemark(parkOut.getOutRemark());
            parkingOrder.setOutUpdateUser(parkOut.getUpdateUser());
            parkingOrder.setParkingSerial(parkOn.getParkingSerial());
            parkingOrder.setParkStatus("out");

            // 计算停车时长
            if (null == parkingOrder.getInTime() && null == parkingOrder.getOutTime()) {
                Date inTime = parkingOrder.getInTime();
                Date outTime = parkingOrder.getOutTime();
                long parkingTime = DateUtil.between(inTime, outTime, DateUnit.SECOND);
                parkingOrder.setParkingTime(parkingTime);
            }

            // 更新数据库中的订单信息
            parkingOrderService.updateById(parkingOrder);

            // 更新ES中的订单数据
            parkingOrderEsService.saveOrUpdate(parkingOrder);
        } else {
            log.warn("[在场记录服务]-手动标记车辆出场，车辆订单记录信息不存在：{}，{}", parkId, parkingSerial);
        }
    }

    /**
     * 批量标记出场
     * 1、批量删除在场记录；
     * 2、批量删除在场的车辆订单和修改历史订单状态为已出场；
     * 3、批量生成出场记录；
     */
    @Override
    public AjaxResult batchMarkOut(BatchMarkOutParkOnRequest request) {
        Long[] ids = request.getRecordIds();
        List<Long> idsArr = Arrays.asList(ids);
        List<ParkOn> parkOnList = parkOnService.selectBatchIds(idsArr);
        // 手动开启事务！
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            for (ParkOn parkOn : parkOnList) {
                String parkingSerial = parkOn.getParkingSerial();

                ParkOut parkOut = new ParkOut();
                parkOut.setInId(parkOn.getInId());
                parkOut.setParkId(SecurityUtils.getParkId());
                parkOut.setCarType(parkOn.getCarType());
                parkOut.setActualFee(0L);
                parkOut.setOutCarPlate(parkOn.getPlate());
                parkOut.setOutLaneId(null);
                // 人工确认出场
                parkOut.setStatus("250");
                parkOut.setOutRemark("人工标记出场");
                parkOut.setOutTime(new Date());
                parkOut.setUpdateUser(SecurityUtils.getUserId());
                parkOut.setIsUpdate("Y");
                parkOut.setParkingSerial(parkingSerial);
                parkOut.setMayFee(0L);
                parkOut.setActualFee(0L);
                parkOut.setReduceFee(0L);
                parkOut.setOweFee(0L);
                parkOut.setCreateTime(new Date());
                parkOut.setUpdateTime(new Date());

                // 判断是否已经存在出场记录
                List<ParkOutDto> parkOutDtoList = parkOutService.selectParkOutByParkingSerial(SecurityUtils.getParkId(), parkingSerial);
                if (CollUtil.isNotEmpty(parkOutDtoList)) {
                    long parkOutId = parkOutDtoList.get(0).getId();
                    // 更新出场记录
                    parkOut.setId(parkOutId);
                    parkOutService.updateParkOut(parkOut);
                } else {
                    // 新增出场记录
                    parkOutService.insertParkOut(parkOut);
                }

                // 修改订单的车辆出场状态
                ParkingOrder parkingOrder = new ParkingOrder();
                parkingOrder.setParkingSerial(parkingSerial);
                parkingOrder.setParkStatus("out");
                parkingOrderService.updateByParkingSerial(parkingOrder);

                // 删除在场的车辆订单
                parkingOrderOnService.deleteParkingOrderOnByParkingSerial(parkingSerial);
            }

            // 批量删除在场记录
            parkOnService.deleteParkOnByIds(idsArr);

            //批量匹配通道订单，匹配到则修改出场状态
            updateLaneOrder(parkOnList.stream().map(ParkOn::getPlate).collect(Collectors.toList()));

            // 提交
            dataSourceTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 回滚
            dataSourceTransactionManager.rollback(transactionStatus);
            return AjaxResult.error("标记离场处理发生异常，请联系管理员！");
        }

        // 标记离场，重新下发余位
        ParkLane parkLane = parkLaneService.selectParkLaneById(parkOnList.get(0).getInLane());
        if (parkLane != null) {
            AjaxResult result = parkGarageCommandService.updateSpaces(parkLane.getParkId(), parkLane.getGarageId());
            if (result.get(AjaxResult.CODE_TAG).equals(HttpStatus.ERROR)) {
                return AjaxResult.error("更新剩余车位命令下发失败，失败原因：" + result.get(AjaxResult.MSG_TAG));
            }
        }
        return AjaxResult.success();
    }

    /**
     * 查询车位信息
     */
    @Override
    public AjaxResult getSpacesInfo() {
        Map<String, Object> map = new HashMap<>(2);
        JSONArray garageArr = new JSONArray();
        Map<String, Long> parkSpaces = parkSpaceBizService.getParkSpaces(SecurityUtils.getParkId());
        ParkGarage parkGarage = new ParkGarage();
        parkGarage.setParkId(SecurityUtils.getParkId());
        List<ParkGarage> garageList = parkGarageService.selectParkGarageList(SecurityUtils.getParkId(), parkGarage);
        for (ParkGarage garage : garageList) {
            JSONObject garageObj = new JSONObject();
            garageObj.put("garageId", garage.getId());
            garageObj.put("totalSpaces", parkSpaceBizService.getGarageTotalSpaces(garage));
            garageObj.put("remainSpaces", parkSpaceBizService.getGarageRemainSpaces(garage));
            garageArr.add(garageObj);
        }
        map.put("park", parkSpaces);
        map.put("garage", garageArr);
        return AjaxResult.success(map);
    }

    /**
     * 查询多辆车在场记录
     * 返回字段的封装
     */
    @Override
    public AjaxResult getOnStatus(Long parkId, List<String> plateArr) {
        ParkOn parkOn = new ParkOn();
        parkOn.setParkId(parkId);
        parkOn.setPlates(plateArr);
        List<ParkOn> list = parkOnService.selectParkOnList(parkOn);
        Map<String, Object> map = new HashMap<>(1);
        map.put(Constants.LIST, list);
        return AjaxResult.success(map);
    }

    @Override
    public AjaxResult detail(EditParkOnRequest request) {
        ParkOn parkOn = parkOnService.selectParkOnAndParkInById(request.getRecordId());
        if (null != parkOn) {
            String inPicObjectName = parkOn.getInPic();
            parkOn.setInPic(StringUtils.isNotEmpty(inPicObjectName) ? aliossUtil.getJzyGetObjectUrl(inPicObjectName).toString() : "");
            ParkOnVo parkOnVo = ParkOnTransform.transformParkOnVo(parkOn);
            Long updateUserId = parkOn.getUpdateUser();
            if (updateUserId != null) {
                R<OperateDTO> result = remoteSystemUserService.getOperate(updateUserId);
                parkOnVo.setUpdateUser(result.getData() != null ? result.getData().getOperateName() : null);
            }
            return AjaxResult.success(parkOnVo);
        } else {
            return AjaxResult.error("未查询到记录");
        }
    }

    @Override
    public AjaxResult importData(Long parkId, List<ParkOn> parkOnList, boolean updateSupport) {
        if (StringUtils.isNull(parkOnList) || parkOnList.isEmpty()) {
            return AjaxResult.error("导入在场数据不能为空");
        }

        // 查询当前车场在场车辆记录
        ParkOn parkOnSearch = new ParkOn();
        parkOnSearch.setParkId(parkId);
        List<ParkOn> parkOnListInDb = parkOnService.selectParkOnList(parkOnSearch);

        StringBuilder importMsg = new StringBuilder();
        List<ParkOn> checkPassList = new ArrayList<>();

        // 校验信息
        for (int i = 0; i < parkOnList.size(); i++) {
            ParkOn parkOn = parkOnList.get(i);
            // 车牌号去空格，转大写
            String plate = parkOn.getPlate();
            plate = plate.replaceAll("\\s*", "").toUpperCase();
            parkOn.setPlate(plate);
            // 车牌非空校验
            if (StrUtil.isBlank(plate)) {
                importMsg.append("第").append(i + 1).append("行数据导入失败，车牌为空").append(Constants.BR);
                continue;
            }
            // 车牌格式校验
            if (!isCarNo(plate)) {
                importMsg.append("第").append(i + 1).append("行数据导入失败，车牌格式错误").append(Constants.BR);
                continue;
            }
            if (parkOn.getInTime() == null) {
                importMsg.append("第").append(i + 1).append("行数据导入失败，进场时间为空").append(Constants.BR);
                continue;
            }
            // 判断当前车辆是否已经在场
            if (!isPlateInList(parkOnListInDb, plate)) {
                // 校验通过信息
                checkPassList.add(parkOn);
            }
        }

        StringBuilder commandMsg = new StringBuilder();
        // 批量手动入场
        if (CollUtil.isNotEmpty(checkPassList)) {
            // 根据parkId获取入口通道ID
            List<ParkLane> parkLaneList = parkLaneService.selectParkLaneByParkId(parkId, LaneTypeEnum.IN.getType());
            if (CollUtil.isNotEmpty(parkLaneList)) {
                long laneId = parkLaneList.get(0).getId();

                // 根据parkId和laneId获取入口通道相机sn
                ParkDeviceNew search = new ParkDeviceNew();
                search.setParkId(parkId);
                search.setLaneId(laneId);
                search.setIsMain(Constants.YES);
                List<ParkDeviceNew> parkDeviceNewList = parkDeviceNewService.selectParkDeviceNewList(search);

                if (CollUtil.isNotEmpty(parkDeviceNewList)) {
                    String sn = parkDeviceNewList.get(0).getCode();

                    for (ParkOn parkOn : checkPassList) {
                        markCarInoutCommandHandler.setPlate(parkOn.getPlate());
                        markCarInoutCommandHandler.setInOrOutTime(DateUtil.formatDateTime(parkOn.getInTime()));
                        // 不开闸
                        markCarInoutCommandHandler.setIsNoOpenGate(1);
                        AjaxResult result = markCarInoutCommandHandler.issue(parkId, sn, "27");
                        if (!result.get(Constants.RESULT_CODE).equals(CommandResultCode.OK)) {
                            // 下发未成功
                            commandMsg.append(parkOn.getPlate()).append("入场信息下发失败，失败原因：").append(result.get(Constants.MESSAGE)).append(Constants.BR);
                        }
                    }
                }
            } else {
                commandMsg.append("入场信息下发失败，车场通道信息查询结果为空");
            }
        } else {
            commandMsg.append("入场信息下发失败，未有校验通过车辆");
        }

        if (StrUtil.isBlank(importMsg) && StrUtil.isBlank(commandMsg)) {
            importMsg.append("在场记录导入成功");
            commandMsg.append("车辆入场信息下发成功");

            return AjaxResult.success(importMsg.append(Constants.BR).append(commandMsg).toString());
        } else {
            return AjaxResult.error(importMsg.append(Constants.BR).append(commandMsg).toString());
        }
    }

    @Override
    public void export(HttpServletResponse response, List<ParkOn> list, String sheetName) {
        // 车辆颜色处理

        // 车辆管理类型处理
        ParkCarMngtype mngtype = new ParkCarMngtype();
        mngtype.setParkId(SecurityUtils.getParkId());
        List<ParkCarMngtype> mngtypeList = parkCarMngtypeService.selectParkCarMngtypeList(mngtype);
        Map<Long, String> curIdNameMap = mngtypeList.stream().collect(Collectors.toMap(ParkCarMngtype::getId, ParkCarMngtype::getName, (value1, value2) -> value1));
        Map<Long, String> curMngTypeMap = mngtypeList.stream().collect(Collectors.toMap(ParkCarMngtype::getId, ParkCarMngtype::getMngType, (value1, value2) -> value1));
        list.forEach(object -> {
            object.setMngTypeName(curIdNameMap.get(object.getCarMngtypeId()));
            object.setMngType(curMngTypeMap.get(object.getCarMngtypeId()));
        });
        // 入场通道名称处理
        ParkLane parkLane = new ParkLane();
        parkLane.setParkId(SecurityUtils.getParkId());
        List<ParkLane> laneList = parkLaneService.selectParkLaneList(parkLane);
        Map<Long, String> laneIdNameMap = laneList.stream().collect(Collectors.toMap(ParkLane::getId, ParkLane::getName, (value1, value2) -> value1));
        list.forEach(object -> object.setLaneName(laneIdNameMap.get(object.getInLane())));
        // 车库名称处理
        ParkGarage parkGarage = new ParkGarage();
        parkGarage.setParkId(SecurityUtils.getParkId());
        List<ParkGarage> garageList = parkGarageService.selectParkGarageList(SecurityUtils.getParkId(), parkGarage);
        Map<Long, String> garageIdNameMap = garageList.stream().collect(Collectors.toMap(ParkGarage::getId, ParkGarage::getName, (value1, value2) -> value1));
        list.forEach(object -> object.setGarageName(garageIdNameMap.get(object.getGarageId())));
        ExcelUtil<ParkOn> util = new ExcelUtil<>(ParkOn.class);
        try {
            util.exportExcel(response, list, sheetName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("导出失败！");
        }
    }

    /**
     * 判断车牌是否在列表中已经存在
     */
    private boolean isPlateInList(List<ParkOn> parkOnList, String plate) {
        boolean res = false;
        if (CollUtil.isNotEmpty(parkOnList)) {
            for (ParkOn on : parkOnList) {
                if (on.getPlate().equals(plate)) {
                    res = true;
                    break;
                }
            }

        }
        return res;
    }

    /**
     * 车牌格式校验
     */
    public static boolean isCarNo(String carNo) {
        String regex = "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[警京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]{0,1}[A-HJ-NP-Z0-9]{4}[A-Z0-9挂学警港澳]{1,2}$";
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(carNo);
        return m.matches();
    }

    @Override
    public void timingDelete() {
        // 查找所有的车场
        List<ParkInfo> parkInfoList = getAllParkInfo();

        if (CollUtil.isNotEmpty(parkInfoList)) {
            parkInfoList.forEach(parkInfo -> {
                long parkId = parkInfo.getId();
                long startTime = System.currentTimeMillis();
                log.info("[在场记录]-定时删除在场记录，开始处理：{},{}", parkId, parkInfo.getName());
                timingDelete(parkId);
                log.info("[在场记录]-定时删除在场记录，处理完成：{},{},耗时{}秒", parkId, parkInfo.getName(), (System.currentTimeMillis() - startTime) / 1000);
            });
        }
    }

    private void timingDelete(Long parkId) {
        // 查询车辆的在场记录保存时间
        ParkConfig parkConfig = parkConfigService.selectParkConfigByParkId(parkId);
        if (parkConfig == null) {
            log.warn("[在场记录]-定时删除在场记录，车场参数为空：{}", parkId);
            return;
        }

        String config = parkConfig.getConfig();
        if (StrUtil.isBlank(config)) {
            log.warn("[在场记录]-定时删除在场记录，车场全局参数为空：{}", parkId);
            return;
        }

        JSONObject configObj = JSON.parseObject(config);
        Integer onRecordSaveDays = configObj.getInteger("dataSaveDays");
        if (null == onRecordSaveDays) {
            log.warn("[在场记录]-定时删除在场记录，在场记录保存时间为空：{}", parkId);
            return;
        }

        log.info("[在场记录]-定时删除在场记录，删除前{}天的在场记录：{}", onRecordSaveDays, parkId);

        int beforeDay = -onRecordSaveDays;
        DateTime beforeDate = DateUtil.offsetDay(new Date(), beforeDay);
        String beforeDateStr = DateUtil.formatDate(beforeDate);
        List<ParkOn> waitDeleteList = parkOnService.selectParkOnListBeforeDay(parkId, beforeDateStr);

        if (CollUtil.isNotEmpty(waitDeleteList)) {
            waitDeleteList.forEach(parkOn -> {
                MarkOutParkOnRequest request = new MarkOutParkOnRequest();
                request.setRecordId(parkOn.getId());
                request.setStatus("251");
                request.setOutRemark("自动清理离场");
                markOut(request);
            });
        }
    }

    /**
     * 获取所有的项目信息
     */
    private List<ParkInfo> getAllParkInfo() {
        return parkInfoService.selectAll();
    }

}
